package org.hxzon.tapestry5.components.base;

import java.lang.annotation.Annotation;
import java.math.BigDecimal;

import org.apache.tapestry5.Binding;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.EventConstants;
import org.apache.tapestry5.FieldTranslator;
import org.apache.tapestry5.FieldValidationSupport;
import org.apache.tapestry5.FieldValidator;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.NullFieldStrategy;
import org.apache.tapestry5.ValidationException;
import org.apache.tapestry5.ValidationTracker;
import org.apache.tapestry5.annotations.BeginRender;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Events;
import org.apache.tapestry5.annotations.Mixin;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.beaneditor.Width;
import org.apache.tapestry5.corelib.base.AbstractField;
import org.apache.tapestry5.corelib.mixins.RenderDisabled;
import org.apache.tapestry5.ioc.AnnotationProvider;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.services.ComponentDefaultProvider;
import org.apache.tapestry5.services.FieldValidatorSource;
import org.apache.tapestry5.services.Request;
import org.slf4j.Logger;

//hxzon:add trim and other validate parameter
//copy from AbstractTextField
@Events({ EventConstants.TO_CLIENT, EventConstants.VALIDATE, EventConstants.PARSE_CLIENT })
public abstract class AbstractTextFieldEx extends AbstractField {
    //parameter for validate--------------------
    @Parameter
    private boolean vRequired;
    @Parameter
    private boolean required;
    @Parameter
    private BigDecimal vMin;
    @Parameter
    private BigDecimal vMax;
    @Parameter(defaultPrefix = BindingConstants.LITERAL)
    private String validate;

    //parameter

    @Parameter(required = true, principal = true)
    @Property
    private Object value;

    @Parameter(value = "true", defaultPrefix = BindingConstants.LITERAL)
    private boolean trim;

    @Parameter(required = true, allowNull = false, defaultPrefix = BindingConstants.TRANSLATE)
    private FieldTranslator<Object> translate;

    @Parameter(defaultPrefix = BindingConstants.NULLFIELDSTRATEGY, value = "default")
    private NullFieldStrategy nulls;
    //inject-----------------------------------
    //change name validate->validator 
    private FieldValidator<Object> validator;

    @Inject
    private Request request;

    @Inject
    private Logger logger;

    @Environmental
    private ValidationTracker tracker;
    @Inject
    private FieldValidationSupport fieldValidationSupport;

    @Inject
    private ComponentResources resources;

    @Inject
    private ComponentDefaultProvider defaultProvider;

    @Inject
    private FieldValidatorSource fieldValidatorSource;

    @Parameter
    private AnnotationProvider annotationProvider;
    //use to render disabled,copy form AbstractTextField
    @SuppressWarnings("unused")
    @Mixin
    private RenderDisabled renderDisabled;

    //default--------------------------------------

    //hxzon:can't remove this,2011-3-25,
    //fix bug when update to tapestry 5.2,but why?
    //copy from AbstractTextField
    final Binding defaultTranslate() {
        return defaultProvider.defaultTranslatorBinding("value", resources);
    }

//Unexpected character '.' at position 4 of input string
//	final Binding defaultValidate() {
//		return defaultProvider.defaultValidatorBinding("value", resources);
//	}
    void initValidator() {
        validator = createValidator();
    }

    //copy from AbstractTextField
    final Binding defaultValue() {
        return defaultProvider.defaultBinding("value", resources);
    }

    //copy from AbstractTextField
    final AnnotationProvider defaultAnnotationProvider() {
        return new AnnotationProvider() {
            public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
                return resources.getParameterAnnotation("value", annotationClass);
            }
        };
    }

    //method-----------------------------------------
    FieldValidator<Object> createValidator() {
        if (resources.isBound("validate")) {
            logger.trace(validate);
            return fieldValidatorSource.createValidators(this, validate);
        } else {
            StringBuilder sb = new StringBuilder();
            if (resources.isBound("vRequired")) {
                sb.append(vRequired ? "required," : "");
            }
            if (resources.isBound("required")) {
                sb.append(required ? "required," : "");
            }
            if (resources.isBound("vMin")) {
                sb.append("min=").append(vMin).append(",");
            }
            if (resources.isBound("vMax")) {
                sb.append("max=").append(vMax).append(",");
            }
            if (sb.length() > 0) {
                sb.deleteCharAt(sb.length() - 1);
            } else {
                sb.append("maxlength=10000");
            }
            String validateStr = sb.toString();
            logger.trace(validateStr);
            return fieldValidatorSource.createValidators(this, validateStr);
        }
    }

    //copy from AbstractTextField
    @BeginRender
    void begin(MarkupWriter writer) {
        initValidator();
        String value = tracker.getInput(this);

        // If this is a response to a form submission, and the user provided a value.
        // then send that exact value back at them.

        if (value == null) {
            // Otherwise, get the value from the parameter ...
            // Then let the translator and or various triggered events get it into
            // a format ready to be sent to the client.

            value = fieldValidationSupport.toClient(this.value, resources, translate, nulls);
        }

        writeFieldTag(writer, value);

        putPropertyNameIntoBeanValidationContext("value");

        translate.render(writer);
        validator.render(writer);

        removePropertyNameFromBeanValidationContext();

        resources.renderInformalParameters(writer);

        decorateInsideField();
    }

    //copy from AbstractTextField
    @Override
    protected void processSubmission(String elementName) {
        //FIXME init validate
        initValidator();
        String rawValue = request.getParameter(elementName);
        if (trim) {
            logger.trace("trim here:" + rawValue);
            rawValue = rawValue.trim();
        }
        tracker.recordInput(this, rawValue);

        try {
            Object translated = fieldValidationSupport.parseClient(rawValue, resources, translate, nulls);

            fieldValidationSupport.validate(translated, resources, validator);

            // If the value provided is blank and we're ignoring blank input
            // (i.e. PasswordField),
            // then don't update the value parameter.

            if (!(ignoreBlankInput() && InternalUtils.isBlank(rawValue)))
                value = translated;
        } catch (ValidationException ex) {
            tracker.recordError(this, ex.getMessage());
        }
    }

    //copy from AbstractTextField
    protected boolean ignoreBlankInput() {
        return false;
    }

    //copy from AbstractTextField
    @Override
    public boolean isRequired() {
        return validator.isRequired();
    }

    //copy from AbstractTextField
    protected final String getWidth() {
        Width width = annotationProvider.getAnnotation(Width.class);

        if (width == null)
            return null;

        return Integer.toString(width.value());
    }

    //copy from AbstractTextField
    protected abstract void writeFieldTag(MarkupWriter writer, String value);
}
